import random
from typing import Any, Dict, List


class Stock_Market:
    def __init__(self) -> None:
        # Hard Coded Names
        short_names = ["IBM", "RCA", "LBJ", "ABC", "CBS"]
        full_names = [
            "INT. BALLISTIC MISSLES",
            "RED CROSS OF AMERICA",
            "LICHTENSTEIN, BUMRAP & JOKE",
            "AMERICAN BANKRUPT CO.",
            "CENSURED BOOKS STORE",
        ]

        # Initializing Dictionary to hold all the information systematically
        self.data: Dict[str, Any] = {}
        for sn, fn in zip(short_names, full_names):
            # A dictionary for each stock
            temp = {"Name": fn, "Price": None, "Holdings": 0}
            # Nested outer dictionary for all stocks
            self.data[sn] = temp

        # Initializing Randomly generated initial prices
        for stock in self.data.values():
            stock["Price"] = round(random.uniform(80, 120), 2)  # Price b/w 60 and 120

        # Initialize Assets
        self.cash_assets = 10000
        self.stock_assets = 0

    def total_assets(self) -> float:
        return self.cash_assets + self.stock_assets

    def _generate_day_change(self) -> None:
        self.changes: List[float] = []
        self.changes.extend(
            round(random.uniform(-5, 5), 2) for _ in range(len(self.data))
        )

    def update_prices(self) -> None:
        self._generate_day_change()
        for stock, change in zip(self.data.values(), self.changes):
            stock["Price"] = round(stock["Price"] + (change / 100) * stock["Price"], 2)

    def print_exchange_average(self) -> None:

        sum = 0
        for stock in self.data.values():
            sum += stock["Price"]

        print(f"\nNEW YORK STOCK EXCHANGE AVERAGE: ${sum / 5:.2f}")

    def get_average_change(self) -> float:
        sum: float = 0
        for change in self.changes:
            sum += change

        return round(sum / 5, 2)

    def print_first_day(self) -> None:

        print("\nSTOCK\t\t\t\t\tINITIALS\tPRICE/SHARE($)")
        for stock, data in self.data.items():
            if stock != "LBJ":
                print(f'{data["Name"]}\t\t\t{stock}\t\t{data["Price"]}')
            else:
                print(f'{data["Name"]}\t\t{stock}\t\t{data["Price"]}')

        self.print_exchange_average()
        self.print_assets()

    def take_inputs(self) -> List[str]:
        print("\nWHAT IS YOUR TRANSACTION IN")
        flag = False
        while not flag:
            new_holdings = []
            for stock in self.data.keys():
                try:
                    new_holdings.append(int(input(f"{stock}? ")))
                except Exception:
                    print("\nINVALID ENTRY, TRY AGAIN\n")
                    break
            if len(new_holdings) == 5:
                flag = self._check_transaction(new_holdings)

        return new_holdings  # type: ignore

    def print_trading_day(self) -> None:

        print("STOCK\tPRICE/SHARE\tHOLDINGS\tNET. Value\tPRICE CHANGE")
        for stock, data, change in zip(
            self.data.keys(), self.data.values(), self.changes
        ):
            value = data["Price"] * data["Holdings"]
            print(
                "{}\t{}\t\t{}\t\t{:.2f}\t\t{}".format(
                    stock, data["Price"], data["Holdings"], value, change
                )
            )

    def update_cash_assets(self, new_holdings) -> None:
        sell = 0
        buy = 0
        for stock, holding in zip(self.data.values(), new_holdings):
            if holding > 0:
                buy += stock["Price"] * holding

            elif holding < 0:
                sell += stock["Price"] * abs(holding)

        self.cash_assets = self.cash_assets + sell - buy

    def update_stock_assets(self) -> None:
        sum = 0
        for data in self.data.values():
            sum += data["Price"] * data["Holdings"]

        self.stock_assets = round(sum, 2)

    def print_assets(self) -> None:
        print(f"\nTOTAL STOCK ASSETS ARE: ${self.stock_assets:.2f}")
        print(f"TOTAL CASH ASSETS ARE: ${self.cash_assets:.2f}")
        print(f"TOTAL ASSETS ARE: ${self.total_assets():.2f}")

    def _check_transaction(self, new_holdings) -> bool:
        sum = 0
        for stock, holding in zip(self.data.values(), new_holdings):
            if holding > 0:
                sum += stock["Price"] * holding

            elif holding < 0:
                if abs(holding) > stock["Holdings"]:
                    print("\nYOU HAVE OVERSOLD SOME STOCKS, TRY AGAIN\n")
                    return False

        if sum > self.cash_assets:
            print(
                "\nYOU HAVE USED ${:.2f} MORE THAN YOU HAVE, TRY AGAIN\n".format(
                    sum - self.cash_assets
                )
            )
            return False

        return True

    def update_holdings(self, new_holdings) -> None:
        for stock, new_holding in zip(self.data.values(), new_holdings):
            stock["Holdings"] += new_holding


def print_instruction() -> None:

    print(
        """
THIS PROGRAM PLAYS THE STOCK MARKET.  YOU WILL BE GIVEN
$10,000 AND MAY BUY OR SELL STOCKS.  THE STOCK PRICES WILL
BE GENERATED RANDOMLY AND THEREFORE THIS MODEL DOES NOT
REPRESENT EXACTLY WHAT HAPPENS ON THE EXCHANGE.  A TABLE
OF AVAILABLE STOCKS, THEIR PRICES, AND THE NUMBER OF SHARES
IN YOUR PORTFOLIO WILL BE PRINTED.  FOLLOWING THIS, THE
INITIALS OF EACH STOCK WILL BE PRINTED WITH A QUESTION
MARK.  HERE YOU INDICATE A TRANSACTION.  TO BUY A STOCK
TYPE +NNN, TO SELL A STOCK TYPE -NNN, WHERE NNN IS THE
NUMBER OF SHARES.  A BROKERAGE FEE OF 1% WILL BE CHARGED
ON ALL TRANSACTIONS.  NOTE THAT IF A STOCK'S VALUE DROPS
TO ZERO IT MAY REBOUND TO A POSITIVE VALUE AGAIN.  YOU
HAVE $10,000 TO INVEST.  USE INTEGERS FOR ALL YOUR INPUTS.
(NOTE:  TO GET A 'FEEL' FOR THE MARKET RUN FOR AT LEAST
10 DAYS)
          ------------GOOD LUCK!------------\n
    """
    )


def main() -> None:
    print("\t\t      STOCK MARKET")
    help = input("\nDO YOU WANT INSTRUCTIONS(YES OR NO)? ")

    # Printing Instruction
    if help.lower() == "yes":
        print_instruction()

    # Initialize Game
    Game = Stock_Market()

    # Do first day
    Game.print_first_day()
    new_holdings = Game.take_inputs()
    Game.update_holdings(new_holdings)
    Game.update_cash_assets(new_holdings)
    print("\n------------END OF TRADING DAY--------------\n")

    response = 1
    while response == 1:

        # Simulate a DAY
        Game.update_prices()
        Game.print_trading_day()
        Game.print_exchange_average()
        Game.update_stock_assets()
        Game.print_assets()

        response = int(input("\nDO YOU WISH TO CONTINUE (YES-TYPE 1, NO-TYPE 0)? "))
        if response == 0:
            break

        new_holdings = Game.take_inputs()
        Game.update_holdings(new_holdings)
        Game.update_cash_assets(new_holdings)
        print("\n------------END OF TRADING DAY--------------\n")

    print("\nHOPE YOU HAD FUN!!!!")
    input()


if __name__ == "__main__":
    main()
